﻿using System;
using System.Linq;
using System.Collections.ObjectModel;
using RLProject1.Model;
using System.ComponentModel;
using System.Windows.Media;
using System.Windows;

namespace RLProject1.ViewModel {
    /// <summary>
    /// Daten für ein einzelnes GridField.
    /// Es wird mit einem <see cref="GridStateQValues"/> initialisiert und stellt Eigenschaften für die Dartellung
    /// in einer View bereit: <br />
    /// QValues, LastUpdated. <br />
    /// LastUpdated liefert den Namen der zuletzt aktualisierten Aktion. Mittels des <see cref="StringActionToAngelConverter"/>
    /// kann dieser String in einem Drehwinkel konvertiert werden (NW = 0°). Die Eigenschaft <code>QValues</code> gibt eine
    /// ObservableCollection mit des lokalen Typs <see cref="LabelValue"/>, welcher neben den Q-Werten noch Visualisierungs-
    /// Eigenschaften beinhaltet.
    /// </summary>
    /// <seealso cref="GridActionTypes"/>
    public class GridFieldViewModel : ViewModelBase {

        private bool _useETraces;
        private static String[] ACTION_STRINGS;
        private double _maxVal = Double.MinValue;
        private Brush _arrowColor;
        //private double[][] _eTraces;
        //private PropertyObserver<GridStateQValues> _qValObserver;
        SarsaForDummies _agent;

        #region Constructors

        /// <summary>
        /// Static Constructor
        /// </summary>
        static GridFieldViewModel() {
            ACTION_STRINGS = Enum.GetNames(typeof(GridActionTypes));
        }

        ///// <summary>
        ///// Initializes a new instance of the <see cref="GridFieldViewModel"/> class.
        ///// </summary>
        ///// <param name="agent">Referenz auf den Agenten</param>
        ///// <param name="qValues">The q values.</param>
        //public GridFieldViewModel(SarsaForDummies agent, GridStateQValues qValues) : this(agent, qValues, null) {
        //}

        /// <summary>
        /// Initializes a new instance of the <see cref="GridFieldViewModel"/> class.
        /// </summary>
        /// <param name="agent">Referenz auf den Agenten</param>
        /// <param name="qValues">The q values.</param>
        /// <param name="eTraces">The decimal traces.</param>
        /// <exception cref="System.ArgumentNullException">qValues or agent is null</exception>
        public GridFieldViewModel(SarsaForDummies agent, GridStateQValues qValues/*, double[][] eTraces*/) {
            if (qValues == null) {
                throw new ArgumentNullException("qValues is null");
            }
            if (agent == null) {
                throw new ArgumentNullException("agent is null");
            }
            _agent = agent;
            _useETraces = agent.EFunction != null;
            //if (eTraces != null) {
            //    setETraces(eTraces);
            //}
            //else {
            //    _useETraces = false;
            //    _eTraces = null;
            //}
            initValuesProperties(qValues);
            _maxVal = qValues.Max();
            colorMaxVals();

            ShowArrow = false;
            Occupied = false;

            //_qValObserver = new PropertyObserver<GridStateQValues>(qValues).RegisterHandler(p => p.N, qVals => qValHandler());
            qValues.PropertyChanged += new PropertyChangedEventHandler(qValues_PropertyChanged);
        }

        //private void qValHandler() {
        //    throw new NotImplementedException();
        //}

        #endregion

        #region Properties

        /// <summary>
        /// Gibt die Q-Werte der Aktionen.
        /// </summary>
        public ObservableCollection<LabelValue> QValues {
            get;
            private set;
        }

        String _last;
        /// <summary>
        /// Gibt oder setzt den Namen der Aktion, die zuletzt aktualisiert wurde.
        /// Im Zusammenhang mit dem <see cref="StringActionToAngelConverter"/> kann dieser Wert
        /// im XAML genutzt werden, um den Richtungspfeil zu drehen.
        /// </summary>
        public String LastUpdated {
            get { return _last; }
            set {
                _last = value;
                OnPropertyChanged("LastUpdated");
            }
        }

        bool _show;
        /// <summary>
        /// Gibt an, ob der Richtungspfeil (in der GUI) angezeigt wird.
        /// </summary>
        /// <value>true, wenn die GUI die gegangene Richtung anzeigen soll, false sonst.</value>
        public bool ShowArrow {
            get { return _show; }
            set {
                _show = value;
                OnPropertyChanged("ShowArrow");
            }
        }

        public Brush ArrowBrush {
            get {
                if (_arrowColor == null)
                    _arrowColor = Brushes.Brown;
                return _arrowColor;
            }
            set { 
                _arrowColor = value;
                OnPropertyChanged("ArrowBrush");
            }
        }

        bool _occupied;
        /// <summary>
        /// Gibt an, ob ein Feld besetzt ist.
        /// </summary>
        public bool Occupied {
            get { return _occupied;}
            set {
                _occupied = value;
                OnPropertyChanged("Occupied");
            }
        }

        #endregion


        //internal void setETraces(double[][] eTraces) {
        //    _eTraces = eTraces;
        //    _useETraces = true;
        //}

        /// <summary>
        /// LabelValues Eigenschaften anhand des GridStateQValues initialisieren.
        /// </summary>
        /// <param name="qValues"></param>
        private void initValuesProperties(GridStateQValues qValues) {
            QValues = new ObservableCollection<LabelValue>();
            //nördliche Werte
            QValues.Add(new LabelValue { Value = qValues.NW, Color = LabelValue.NORMCOLOR, VAlignment = VerticalAlignment.Top });
            QValues.Add(new LabelValue { Value = qValues.N, Color = LabelValue.NORMCOLOR, VAlignment = VerticalAlignment.Top });
            QValues.Add(new LabelValue { Value = qValues.NO, Color = LabelValue.NORMCOLOR, VAlignment = VerticalAlignment.Top });

            //mittlere Werte
            QValues.Add(new LabelValue { Value = qValues.W, Color = LabelValue.NORMCOLOR, VAlignment = VerticalAlignment.Center });
            QValues.Add(new LabelValue { Value = Double.MinValue, Color = LabelValue.INVISIBLE, VAlignment = VerticalAlignment.Center });
            QValues.Add(new LabelValue { Value = qValues.O, Color = LabelValue.NORMCOLOR, VAlignment = VerticalAlignment.Center });

            //südliche Werte
            QValues.Add(new LabelValue { Value = qValues.SW, Color = LabelValue.NORMCOLOR, VAlignment = VerticalAlignment.Bottom });
            QValues.Add(new LabelValue { Value = qValues.S, Color = LabelValue.NORMCOLOR, VAlignment = VerticalAlignment.Bottom });
            QValues.Add(new LabelValue { Value = qValues.SO, Color = LabelValue.NORMCOLOR, VAlignment = VerticalAlignment.Bottom });
        }



        /// <summary>
        /// Wenn ein Q-Wert des zugehörigen <see cref="GridStateQValues"/> aktualisiert wird,
        /// wird hier die lokale Eigenschaft LastUpdated und das private Feld _maxVal angepasst.
        /// Hat sich der max-Wert verändert, werden die Farben zur Darstellung angepasst.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void qValues_PropertyChanged(object sender, PropertyChangedEventArgs e) {
            String prop = e.PropertyName;
            GridStateQValues sdr = sender as GridStateQValues;
            if (sdr == null)
                return;
            else if (ACTION_STRINGS.Contains<String>(prop)) {
                switch (prop) {
                    case "N":
                        // Wenn der Q-Wert sich geändert hat, übernehme ihn in der Anzeige
                        if (sdr.N != QValues[1].Value) {
                            QValues[1].Value = sdr.N;
                            if (!ShowArrow)
                                ShowArrow = true;
                        }
                        break;
                    case "NO":
                        if (sdr.NO != QValues[2].Value) {
                            QValues[2].Value = sdr.NO;
                            if (!ShowArrow)
                                ShowArrow = true;
                        }
                        break;
                    case "O":
                        if (sdr.O != QValues[5].Value) {
                            QValues[5].Value = sdr.O;
                            if (!ShowArrow)
                                ShowArrow = true;
                        }
                        break;
                    case "SO":
                        if (sdr.SO != QValues[8].Value) {
                            QValues[8].Value = sdr.SO;
                            if (!ShowArrow)
                                ShowArrow = true;
                        }
                        break;
                    case "S":
                        if (sdr.S != QValues[7].Value) {
                            QValues[7].Value = sdr.S;
                            if (!ShowArrow)
                                ShowArrow = true;
                        }
                        break;
                    case "SW":
                        if (sdr.SW != QValues[6].Value) {
                            QValues[6].Value = sdr.SW;
                            if (!ShowArrow)
                                ShowArrow = true;
                        }
                        break;
                    case "W":
                        if (sdr.W != QValues[3].Value) {
                            QValues[3].Value = sdr.W;
                            if (!ShowArrow)
                                ShowArrow = true;
                        }
                        break;
                    case "NW":
                        if (sdr.NW != QValues[0].Value) {
                            QValues[0].Value = sdr.NW;
                            if (!ShowArrow)
                                ShowArrow = true;
                        }
                        break;
                    default:
                        break;
                }
                setArrowBrush(prop, sdr);
                LastUpdated = prop;
                //double _maxOld = _maxVal;
                _maxVal = sdr.Max();
                colorMaxVals();
            }
        }

        /// <summary>
        /// Färbt den Pfadpfeil in Abhängigkeit des Eligibilitywertes.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="sdr"></param>
        private void setArrowBrush(String prop, GridStateQValues sdr) {
            if (!_useETraces)
                return;
            var gaType = (GridActionTypes)Enum.Parse(typeof(GridActionTypes), prop);
            double eligibility = _agent.EFunction[(GridAction)gaType, sdr.State];
            // Farbe nach eWert:
            if (eligibility >= 0.95)
                ArrowBrush = Brushes.Black;
            else if (eligibility >= 0.85)
                ArrowBrush = Brushes.Maroon;
            else if (eligibility >= 0.65)
                ArrowBrush = Brushes.Brown;
            else if (eligibility >= 0.55)
                ArrowBrush = Brushes.Chocolate;
            else if (eligibility >= 0.4)
                ArrowBrush = Brushes.Coral;
            else if (eligibility >= 0.25)
                ArrowBrush = Brushes.DarkSalmon;
            else if (eligibility >= 0.1)
                ArrowBrush = Brushes.LightSlateGray;
            else if (eligibility >= 0.05)
                ArrowBrush = Brushes.LightSteelBlue;
            else
                ArrowBrush = Brushes.LightGray;
            return;
        }


        /// <summary>
        /// Passt die Farben der <see cref="LabelValue"/> in den ~Values-Eigenschaften dem _maxVal an.
        /// </summary>
        void colorMaxVals() {
            foreach (LabelValue item in QValues) {
                if (item.Value == Double.MinValue)
                    continue;
                else if (item.Value == _maxVal)
                    item.Color = LabelValue.BESTCOLOR;
                else
                    item.Color = LabelValue.NORMCOLOR;
            }
        }


        protected override void OnDispose() {
            QValues.Clear();
        }



        /// <summary>
        /// Hilfsklasse zur Darstellung eines QVals in einem Label o.ä. mit PropertyChange-Support.
        /// <para>
        /// Value enthält den Wert (einen Q(s,a)-Wert) und Color liefert eine Farbe, die für den Schriftfont genutzt werden kann.
        /// </para>
        /// </summary>
        public class LabelValue : INotifyPropertyChanged {

            /// <summary>System.Windows.Brushes.Red</summary>
            public static readonly Brush BESTCOLOR = Brushes.Red;
            /// <summary>Black</summary>
            public static readonly Brush NORMCOLOR = Brushes.Black;
            /// <summary>Transparent</summary>
            public static readonly Brush INVISIBLE = Brushes.Transparent;

            #region INotifyPropertyChanged Members

            public event PropertyChangedEventHandler PropertyChanged;

            void onPropertyChanged(String propName) {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }

            #endregion

            #region Properties
            
            double _value;
            public double Value {
                get { return _value; }
                set {
                    _value = value;
                    onPropertyChanged("Value");
                }
            }

            Brush _brush;
            public Brush Color { 
                get{ return _brush; } 
                set{
                    _brush = value;
                    onPropertyChanged("Color");
                }
            }

            VerticalAlignment _screenVAlignment;
            public VerticalAlignment VAlignment {
                get { return _screenVAlignment; }
                set {
                    _screenVAlignment = value;
                    onPropertyChanged("VAlignment");
                }
            }

            #endregion

            #region Object Overrides
            
            public override bool Equals(object obj) {
                if (obj is LabelValue)
                    return ((LabelValue)obj).Value == Value;
                return false;
            }

            public override int GetHashCode() {
                return Value.GetHashCode();
            }

            public override string ToString() {
                return "LabelValue[" + Value + "," + Color.ToString() + "]";
            }

            #endregion
        }
    }

    
}
